home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 7 / Apprentice-Release7.iso / Source Code / Libraries / VideoToolbox 97.08.16 / VideoToolboxSources / Rush.c < prev    next >
Encoding:
C/C++ Source or Header  |  1997-08-08  |  15.8 KB  |  440 lines  |  [TEXT/CWIE]

  1. /* Rush.c
  2.  
  3. COPYRIGHT:
  4. Copyright Denis Pelli, 1997. This file may be distributed freely as long
  5. as this notice accompanies it and any changes are noted in the source. 
  6. However, it may not be sold, in source or compiled form, without permission.
  7. It is distributed as is, without any warranty implied or provided.  We
  8. accept no liability for any damage or loss resulting from the use of
  9. this software.
  10.  
  11. PURPOSE:
  12. Run any critical user function with minimal interruption by Mac OS
  13. system software. Without Rush, the Mac OS and device drivers steal
  14. chunks of time whenever they like. E.g. the Zip disk driver, when no
  15. disk is inserted, steals 2 ms once every 3 seconds. For a loop showing a
  16. real-time movie, the possibility of losing 2 milliseconds, out of a
  17. frame time of say 13 ms, substantially reduces the maximum number of
  18. pixels that can be shown per frame if we insist on never missing a
  19. frame. Rush implements two alternate solutions. Apple's guidelines
  20. suggest that driver interrupt tasks should be very brief, passing any
  21. lengthy tasks to the Deferred Task Manager to run later. When
  22. priorityLevel is -1, Rush runs the user code as a deferred task, which
  23. runs normally, except that all other deferred tasks are blocked until
  24. our code finishes. When priorityLevel is 0 the user code runs normally.
  25. When priorityLevel is >0 the user code runs at that raised processor
  26. priority. The Deferred Task Manager doesn't run at all until the
  27. processor priority goes back to zero. Thus any nonzero priorityLevel
  28. will block all deferred tasks. A positive priorityLevel will also block
  29. some primary interrupt tasks; more are blocked the higher the
  30. priorityLevel. At priorityLevel 7 nearly all interrupts are blocked.
  31.  
  32. When priorityLevel is nonzero we block all the Mac's deferred tasks
  33. while the user function is run. When priorityLevel is 1 (or more) we
  34. block deferred tasks by raising the priority before calling the user
  35. function. When priorityLevel is -1 we block deferred tasks by calling
  36. the user function from within a deferred task.
  37.  
  38. The deferred-task solution is attractive. All deferred tasks are blocked
  39. until ours finishes, and all urgent interrupt processes (including VBL
  40. and Time Manager updates) occur normally. And keyboard and mouse still
  41. work. This elegant solution was suggested by Bo3b Johnson at Apple
  42. Developer Support on 4/19/97 (Follow-up:  418098). 
  43.  
  44. A slight drawback is that because the deferred task is interrupt driven,
  45. we must save and restore the 68881 floating point registers (if we're
  46. compiled GENERATING68881) to allow the user's task to use floating
  47. point. Apple says the save/restore takes about 50 times as long as a
  48. MOVE. This overhead might lessen the attractiveness of the deferred-task
  49. solution (priorityLevel -1) when running on 68K machines, but I suspect
  50. that the overhead will be acceptable in most applications.
  51.  
  52. The raised-priority solution has a different drawback. Raising priority
  53. (to 1 or more) does block the deferred tasks, but also freezes the mouse
  54. and keyboard, and results in abnormal operation of the Time Manager
  55. (overflow and coarse steps) and. The loss of the Time Manager usually
  56. doesn't matter on PCI PowerMacs, because Seconds.c and WaitSeconds.c
  57. then use the excellent UpTime routine, which is immune to priorityLevel.
  58.  
  59. 4/22/97 Testing with MATLAB LoopTest.m indicates that Rush.mex runs fine
  60. at all priorities. However, a 2 ms interruption due to the Zip driver
  61. (when no disk is inserted) occurs about one every three seconds at
  62. priorities -1, 0, and 1, and only disappears at priorities 2 and higher.
  63. Contrary to Apple's rules, the Zip driver's 2 ms interruption is NOT a
  64. deferred task. We're hoping that Iomega will fix their driver soon. For
  65. news you might look here:
  66. <http://www.macintouch.com/jazprobs.html>
  67.  
  68. A THOUGHT ON THE 68881:
  69. Hmm. I wonder. In fact we know for certain that we're interrupting
  70. our own wait loop:
  71.     while(!stuff.done) ;    // wait until our deferredTask is done
  72. Neither this loop nor the function that it's in have any floating point,
  73. so we know that the interrupt hasn't interrupted any floating-point
  74. code. So the usual assumptions of a subroutine about the prior state of
  75. the FPU are probably valid. The only exception that occurs to me is if
  76. our wait loop is interrupted and that interrupt process is itself
  77. interrupted. However, even in that case we'd only get into trouble if
  78. the interrupted interrupt process was using the 68881 fpu, which is
  79. extremely unlikely because Apple discourages it so strongly (because of
  80. the overhead of saving & restoring the fpu). Thus, we could probably
  81. safely comment out the calls to Save68881 and Restore68881, but I don't
  82. want to do that without testing, and I have little incentive at this
  83. point. If you find that the save/restore overhead is not negligible in
  84. your application, then you may want to try that. If you confirm that
  85. it's safe to ride without these training wheels, please let me know.
  86.  
  87. EXAMPLE 1:
  88. Let's rush the calculation of c=a+b. (This simple example is solely for
  89. the sake of exposition. I can't think of any reason to rush this. In
  90. real life you'll only want to rush code that must synchronize with
  91. external events, e.g. displaying or recording.)
  92.  
  93. typedef struct {
  94.      int a,b,c;
  95. } Abc;
  96.  
  97. void main(void)
  98. {
  99.      Abc abc;
  100.      int priorityLevel=7;
  101.  
  102.      abc.a=a;
  103.      abc.b=b;
  104.      Rush(priorityLevel,&Summer,&abc);
  105.      c=abc.c;
  106. }
  107.  
  108. void Summer(void *argPtr)
  109. {
  110.      Abc *abcPtr;
  111.  
  112.      abcPtr=argPtr;
  113.      abcPtr->c = abcPtr->a + abcPtr->b;
  114. }
  115.  
  116. EXAMPLE 2:
  117. This example illustrates something closer to the use for which Rush.c
  118. was created. Here we call back to the main application (i.e. MATLAB) to
  119. rush high-level code. The C subroutine called "mexFunction" is a MEX
  120. function, callable from MATLAB programs, e.g. Rush('c=a+b'). The
  121. mexFunction receives a string of MATLAB code, e.g. "c=a+b". Our rushed C
  122. routine, fun(), asks the MATLAB application to use "eval" to execute the
  123. string, which has the effect of rushing this bit of MATLAB code. Again,
  124. in real life you'll only want to rush code that must synchronize with
  125. external events. In my research, we now Rush all our MATLAB display
  126. loops, to produce frame-accurate real-time movies. The call to
  127. mexSetTrapFlag (a MATLAB internal routine) is significant. It tells
  128. MATLAB, if it encounters an error, to nevertheless return here to the
  129. caller. It would be bad for MATLAB to print an error message and stop
  130. without returning, because the priorityLevel may have frozen the
  131. keyboard and mouse, and a deferred task will hang forever if it tries to
  132. access the disk. As implemented here, MATLAB always returns, even if it
  133. encounters an error, so everything's hunky dory.
  134.  
  135. typedef struct{
  136.     mxArray *mxString;
  137.     long error;
  138. } Stuff;
  139.  
  140. void mexFunction(int nlhs, mxArray *plhs[], int nrhs, CONSTmxArray *prhs[])
  141. {
  142.     Stuff stuff;
  143.     
  144.     stuff.mxString=prhs[0];
  145.     stuff.error=0;
  146.     Rush(priorityLevel,&fun,&stuff);
  147. }
  148.  
  149. void fun(void *argPtr)
  150. {
  151.     Stuff *stuffPtr;
  152.     mxArray *plhs[1],*prhs[1];
  153.     
  154.     stuffPtr=(Stuff *)argPtr;
  155.     prhs[0]=stuffPtr->mxString;
  156.     mexSetTrapFlag(1);
  157.     stuffPtr->error=mexCallMATLAB(0,plhs,1,prhs,"eval");
  158.     mexSetTrapFlag(0);
  159. }
  160.  
  161. HISTORY:
  162. 4/21/97        dgp        Wrote it as a MEX file.
  163. 4/22/97        dgp        Polished the code. Marked the done flag as "volatile", 
  164.                     since it's set by the deferred task at interrupt time.
  165. 5/2/97        dgp     Fixed bug in definition of GetA1 declaration to fix 68K crash reported by dhb.
  166. 6/22/97        dgp        Updated comments above.
  167. 7/1/97        dgp        As requested by Josh Solomon, removed the mex-specific stuff to leave this
  168.                     generic version that i'm adding to the VideoToolbox. Josh wants to use
  169.                     it to create a RUSH facility in Mathematica, like the one I created in MATLAB.
  170. 7/9/97        dgp        Now save and restore the 68K fpu registers (e.g. 68881) when necessary.
  171.                     This allows the user's task to safely use floating point. The
  172.                     saving and restoring is only necessary when GENERATING68881 is true and the
  173.                     priorityLevel is -1. The need for this is explained in Apple Technote hw22.
  174.                     <http://devworld.apple.com/dev/technotes/hw/hw_22.html>
  175. 7/31/97        dgp        Polished comments. Added conditionals for MATLAB, which avoid calling GetPriority, by maintaining
  176.                     our own copy of the priorityLevel. This is based on the impression I had, while debugging, that
  177.                     calling GetPriority occassionally crashed. I'm not sure that impression was right, but this approach
  178.                     worked well when I tested the previous incarnation of RUSH, so i'm inclined to keep doing it this way.
  179.                     David mentioned that the loops were running a bit slow on 68K, so I'm disabling the 68881 saving, since
  180.                     I think it's superfluous because I know that the code being interrupted doesn't use floating point.
  181. 8/8/97        dhb        Force include of PsychToolbox.h if MATLAB is defined.
  182. KNOWN BUGS:
  183. */
  184. #define NEED_TO_SAVE_FPU 0
  185. #ifdef MATLAB
  186.     #include <PsychLibSources.h>
  187. #else
  188.     #include <VideoToolbox.h>
  189. #endif
  190. #ifndef __PROCESSES__
  191.     #include <Processes.h>    // ProcessSerialNumber
  192. #endif
  193. #ifndef __TRAPS__
  194.     #include <Traps.h>        // _DTInstall
  195. #endif
  196. #define CODE_RESOURCE MATLAB
  197. #if GENERATING68K
  198.     #pragma parameter __D0 GetA1()
  199.     long GetA1(void)= 0x2009;    // MOVE.L A1,D0
  200.     #pragma parameter __D0 GetA4()
  201.     long GetA4(void)= 0x200C;    // MOVE.L A4,D0
  202.     #pragma parameter __D0 GetA5()
  203.     long GetA5(void)= 0x200D;    // MOVE.L A5,D0
  204.     #if THINK_C
  205.         #pragma parameter __D0 SetA4(__D0)
  206.         long SetA4(long) = 0xC18C;    // EXG D0,A4
  207.     #else
  208.         long SetA4(long:__D0):__D0 = 0xC18C;    // EXG D0,A4
  209.     #endif
  210. #else
  211.     #define GetA1()        0L
  212.     #define GetA4()        0L
  213.     #define GetA5()        0L
  214.     #define SetA4(x)    0L
  215. #endif
  216.  
  217. // Inline code to save and restore FPU on 68881 etc.
  218. // This is needed if we've arrived where we are by an interrupt
  219. // that may be interrupting a floating point calculation.
  220. // See Apple's Technote HW22 "Cooperating with the coprocessor"
  221. // <http://devworld.apple.com/dev/technotes/hw/hw_22.html>
  222. void Save68881(void);
  223. void Restore68881(void);
  224. #if GENERATING68881
  225.     void Save68881(void)={0xF327,0xF227,0xE0FF};    // FSAVE -(A7)            ; save FPU state
  226.                                                     // FMOVEM FP0-FP7,-(A7)    ; save FPU regs
  227.     void Restore68881(void)={0xF21F,0xD0FF,0xF35F};    // FMOVEM (A7)+,FP0-FP7    ; restore FPU regs
  228.                                                     // FRESTORE (A7)+        ; restore FPU state
  229. #endif
  230.  
  231. typedef struct{
  232.     ProcessSerialNumber psn;
  233.     DeferredTask *deferredTaskPtr;
  234.     volatile long A,error,failedAttempts;
  235.     void (*functionPtr)(void *argPtr);
  236.     void *argPtr;
  237.     volatile Boolean done;
  238.     int priorityLevel;
  239. } Stuff;
  240. #if GENERATINGPOWERPC
  241.     static void OurDeferredTask(register Stuff *stuffPtr);
  242. #else
  243.     static void OurDeferredTask(void);
  244. #endif
  245. static void CallFunction(Stuff *stuffPtr);
  246.  
  247. int Rush(int priorityLevel,void (*functionPtr)(void *argPtr),void *argPtr);
  248.  
  249. int Rush(int priorityLevel,void (*functionPtr)(void *argPtr),void *argPtr)
  250. {
  251.     static Boolean firstTime=1,deferAvailable;
  252.     static DeferredTaskUPP deferredTaskUPP;
  253.     int oldPriority;
  254.     DeferredTask deferredTask;
  255.     int error;
  256.     Stuff stuff;
  257.     
  258.     if(firstTime){
  259.         deferAvailable=TrapAvailable(_DTInstall);
  260.         deferredTaskUPP=NewDeferredTaskProc(OurDeferredTask);
  261.         firstTime=0;
  262.     }
  263.     if(priorityLevel<-1 || priorityLevel>7)PrintfExit("%s: Illegal priorityLevel %d.",__FILE__,priorityLevel);
  264.     error=GetCurrentProcess(&stuff.psn);
  265.     stuff.functionPtr=functionPtr;
  266.     stuff.argPtr=argPtr;
  267.     stuff.done=0;
  268.     stuff.error=0;
  269.     stuff.failedAttempts=0;
  270.     stuff.deferredTaskPtr=NULL;
  271.     stuff.priorityLevel=priorityLevel;
  272.     switch(priorityLevel){
  273.     case -1:
  274.         // Run user's function as a deferred task.
  275.         if(!deferAvailable)PrintfExit("%s: Your System is too old: no Deferred Task Manager.",__FILE__);
  276.         #if MATLAB
  277.             oldPriority=GetPsychTable()->priority;
  278.         #else
  279.             oldPriority=GetPriority();
  280.         #endif
  281.         if(oldPriority>0){
  282.             #if MATLAB
  283.                 SetPsychPriority(0);
  284.             #else
  285.                 oldPriority=SetPriority(0);
  286.             #endif
  287.             PrintfExit("%s: Can't defer task while processor priority is above zero.",__FILE__);
  288.         }
  289.         #if CODE_RESOURCE
  290.             stuff.A=GetA4();
  291.         #else
  292.             stuff.A=GetA5();
  293.         #endif
  294.         stuff.deferredTaskPtr=&deferredTask;
  295.         deferredTask.qType=dtQType;
  296.         deferredTask.dtAddr=deferredTaskUPP;
  297.         deferredTask.dtReserved=0;
  298.         deferredTask.dtParam=(long)&stuff;
  299.         error=DTInstall(stuff.deferredTaskPtr);
  300.         if(error)PrintfExit("%s: DTInstall error %d.",__FILE__,error);
  301.         while(!stuff.done) ;    // wait until our deferredTask is done
  302.         break;
  303.     case 0:
  304.         // Run user's function normally. 
  305.         CallFunction(&stuff);
  306.         break;
  307.     default:
  308.         // Run user's function at raised processor priority.
  309.         #if MATLAB
  310.             oldPriority=GetPsychTable()->priority;
  311.             SetPsychPriority(priorityLevel);
  312.         #else
  313.             oldPriority=GetPriority();
  314.             SetPriority(priorityLevel);
  315.         #endif
  316.         CallFunction(&stuff);
  317.         #if MATLAB
  318.             SetPsychPriority(oldPriority);
  319.         #else
  320.             SetPriority(oldPriority);
  321.         #endif
  322.         break;
  323.     }
  324.     if(stuff.error==1234)
  325.         PrintfExit("%s: user function never ran; always interrupted wrong process.",__FILE__);
  326.     if(stuff.failedAttempts)
  327.         printf("%s: WARNING: user function ran only after %ld attempts that interrupted other processes.\n",__FILE__,stuff.failedAttempts);
  328.     return stuff.failedAttempts;
  329. }
  330.  
  331. #if (THINK_C || THINK_CPLUS || SYMANTEC_C)
  332.     #pragma options(!profile)    // it would be dangerous to call the profiler from here
  333.     #pragma options(assign_registers,redundant_loads)
  334. #endif
  335. #if __MWERKS__ && __profile__
  336.     #pragma profile off            // on 68k it would be dangerous to call the profiler from here
  337. #endif
  338.  
  339. #if GENERATINGPOWERPC
  340.     static void OurDeferredTask(register Stuff *stuffPtr)
  341.     {
  342.         CallFunction(stuffPtr);
  343.     }
  344. #else
  345.     // The saving/restoring of 68881 wouldn't work if OurDeferredTask had any C use of floating point, because
  346.     // in that case the compiler would insert floating point instructions at the beginning of the routine,
  347.     // inevitably BEFORE we attempt to save the state, defeating our purpose. It's ok for the CallFunction() or the
  348.     // routine(s) that it calls to use floating point.
  349.     static void OurDeferredTask(void)
  350.     {
  351.         Stuff *stuffPtr;
  352.         long oldA;
  353.  
  354.         stuffPtr=(void *)GetA1();
  355.         #if CODE_RESOURCE
  356.             oldA=SetA4(stuffPtr->A);
  357.         #else
  358.             oldA=SetA5(stuffPtr->A);
  359.         #endif
  360.         #if GENERATING68881 && NEED_TO_SAVE_FPU
  361.             Save68881();
  362.         #endif
  363.         CallFunction(stuffPtr);
  364.         #if GENERATING68881 && NEED_TO_SAVE_FPU
  365.             Restore68881();
  366.         #endif
  367.         #if CODE_RESOURCE
  368.             SetA4(oldA);
  369.         #else
  370.             SetA5(oldA);
  371.         #endif
  372.     }
  373. #endif
  374.  
  375. static void CallFunction(Stuff *stuffPtr)
  376. {
  377.     ProcessSerialNumber psn;
  378.     Boolean isOurs;
  379.     int error;
  380.  
  381.     error=GetCurrentProcess(&psn);
  382.     error=SameProcess(&stuffPtr->psn,&psn,&isOurs);    // compare psns.
  383.     if(isOurs){
  384.         // It's safe. Let's do it.
  385.         (*stuffPtr->functionPtr)(stuffPtr->argPtr);
  386.         stuffPtr->error=0;
  387.         stuffPtr->done=1;
  388.     }else{
  389.         // Oops. We're interrupting some other process. It's unsafe to run now.
  390.         // Let's lie low, and reinstall ourselves to try again later.
  391.         stuffPtr->failedAttempts++;
  392.         if(stuffPtr->failedAttempts<10 && stuffPtr->deferredTaskPtr!=NULL)
  393.             stuffPtr->error=DTInstall(stuffPtr->deferredTaskPtr);
  394.         else stuffPtr->error=1234;
  395.         if(stuffPtr->error)stuffPtr->done=1;    // Can't do it; give up.
  396.     }
  397. }
  398.  
  399.  
  400. #if 0
  401. // This code is copied from Apple Technote HW22 "Cooperating with the Coprocessor"
  402. // http://devworld.apple.com/dev/technotes/hw/hw_22.html
  403. // DON'T try to run these. They're here solely to get a disassembly. The assembly
  404. // code must be run as inline code, which the definitions at the top of this
  405. // file accomplish, based on the THINK C disassemblies of these functions.
  406. // THINK C knows floating point assembly directives; CodeWarrior 10 doesn't.
  407.  
  408. void Save68881(void);
  409. void Restore68881(void);
  410.  
  411. void Save68881(void){
  412.     asm{
  413.         FSAVE        -(SP)            ; save the FP state
  414.         FMOVEM.X    FP0-FP7,-(SP)    ; save the FP regs we use
  415.     }
  416. }
  417.  
  418. void Restore68881(void){
  419.     asm {
  420.         FMOVEM.X    (SP)+,FP0-FP7    ; replace the FP regs we used
  421.         FRESTORE    (SP)+            ; restore the FP state
  422.     }
  423. }
  424. #endif
  425.  
  426. /*
  427. As disassembled by THINK C. Note that we got FMOVEM not FMOVEM.X. I suspect that
  428. this difference doesn't matter here, but I don't really know. Maybe it's not a difference
  429. at all.
  430.  
  431. Save68881:
  432. 00000000: F327               FSAVE     -(A7)
  433. 00000002: F227 E0FF          FMOVEM    FP0-FP7,-(A7)
  434.  
  435. Restore68881:
  436. 00000000: F21F D0FF          FMOVEM    (A7)+,FP0-FP7
  437. 00000004: F35F               FRESTORE  (A7)+
  438. */
  439.  
  440.